package cz.vutbr.fit.dnacompress.encoding;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.biojava.bio.BioException;
import org.biojava.bio.seq.DNATools;
import org.biojava.bio.seq.io.SymbolTokenization;
import org.biojava.bio.symbol.FiniteAlphabet;
import org.biojava.bio.symbol.IllegalSymbolException;

import cz.vutbr.fit.dnacompress.events.AlignDeletitionEvent;
import cz.vutbr.fit.dnacompress.events.AlignEvent;
import cz.vutbr.fit.dnacompress.events.AlignInsertionEvent;
import cz.vutbr.fit.dnacompress.events.AlignMutationEvent;

/**
 * zakodovani ve forme prosteho textu
 */
public class StringCoder extends AbstractCoder {

	// finite automata states
	private static final int	START = 0;
    private static final int    NUMBER = 1;
    private static final int 	INSERTION_START = 2;
    private static final int   	ALPHABET = 3;
    private static final int   	DELETITION_START = 4;
    private static final int   	DELETITION = 5;
    private static final int   	DELETITION_END = 6;
    private static final int	ERROR = 7;
    
    // finite states
    private static Integer[] finiteStates = { START, ALPHABET, DELETITION_END};
	
    // compare characters
    private static final char	INSERTION_DELIMITER = 'i';
    private static final char	DELETITION_DELIMITER = 'd';
    private static final char	DELETITION_FINISHER = 'd';
    
    // konecne stavy
    private List<Integer> finiteStatesList = null;
    
    // abeceda DNA
    private FiniteAlphabet dna = DNATools.getDNA();
    // tokenizer DNA symbolu
	private SymbolTokenization st = null;
	
	public StringCoder() throws BioException {
		
		coderID = 1;
		finiteStatesList = Arrays.asList(finiteStates);
		st = dna.getTokenization("token");
	}
	
	@Override
	public List<AlignEvent> decode( InputStream is) {
		
		System.out.println( "=== Parse cbr file ===");
		List<AlignEvent> events = new ArrayList<AlignEvent>();
		
		// parsovani cbr souboru pomoci automatu
		try{
			BufferedReader br = new BufferedReader( new InputStreamReader( is));			
			
			int readInput;
			char readCharacter;
			char stateCharacter = 0;
			StringBuilder numberBuilder = new StringBuilder();
			int position = 0;
			
			AlignEvent actualEvent = null;
			 
			int offset = 0;
			int state = START;
			int nextState = 0;
			
			System.out.println( "parsing data...");
			//Read File Line By Line
			for(;;) {
				// read next character
				readInput = br.read();
				
				// cast to character because of states requirments
				readCharacter = (char) readInput;
				
				// states of finite automata
				switch( state) {
					
					case START:
						if( isNextEvent( readCharacter)) {
							nextState = NUMBER;
						} else {
							nextState = ERROR;
						}
						break;
						
					case NUMBER:
						// make position
						numberBuilder.append( stateCharacter);
						
						// position maker loop
						if( Character.isDigit( readCharacter)) {
							nextState = NUMBER;
						// not number = end of position maker	
						} else {
							// make position from builded string
							position = Integer.parseInt( numberBuilder.toString());
							// clear builder
							numberBuilder = new StringBuilder();
							
							if( readCharacter == INSERTION_DELIMITER) {
								// create new insertion event
								actualEvent = new AlignInsertionEvent(position);
								nextState = INSERTION_START;
							} else if( readCharacter == DELETITION_DELIMITER) {
								
								// create new deletition event
								actualEvent = new AlignDeletitionEvent(position);
								nextState = DELETITION_START;
							} else if( isValidDNASymbol( readCharacter)) {
								
								// create new mutation event a add first symbol
								actualEvent = new AlignMutationEvent(position);
								nextState = ALPHABET;
							} else {
								nextState = ERROR;
							}
						}
						break;
						
					case INSERTION_START:
						// jump to insertion loop
						if( isValidDNASymbol(readCharacter)) {
							nextState = ALPHABET;
						} else {
							nextState = ERROR;
						}
						break;
						
					case ALPHABET:
						// filling insertion/mutation event
						actualEvent.addData( stateCharacter);
						
						// end of insertion/mutation event
						if( isNextEvent( readCharacter)) {
							// adding complete event to events array
							events.add( actualEvent);
							// destruct event
							actualEvent = null;
							
							nextState = NUMBER;
						// mutation event loop	
						} else if( isValidDNASymbol( readCharacter)) {
							nextState = ALPHABET;
						} else {
							nextState = ERROR;
						}
						break;
						
					case DELETITION_START:
						// jump to insertion loop
						if( Character.isDigit(readCharacter)) {
							nextState = DELETITION;
						} else {
							nextState = ERROR;
						}
						break;
						
					case DELETITION:
						
						// make deletition length
						numberBuilder.append( stateCharacter);
						
						// deletition count loop
						if( Character.isDigit( readCharacter)) {
							
							nextState = DELETITION;
						// end of deletition length	
						} else if( readCharacter == DELETITION_FINISHER) {
							
							nextState = DELETITION_END;
						} else {
							nextState = ERROR;
						}
						break;

					case DELETITION_END:
						// make deletition length from builded string
						actualEvent.setData( Integer.parseInt( numberBuilder.toString()));
						// adding complete event to events array
						events.add( actualEvent);
						// destruct event
						actualEvent = null;
						// clear builder
						numberBuilder = new StringBuilder();
						
						if( isNextEvent( readCharacter)) {
							nextState = NUMBER;
						} else {
							nextState = ERROR;
						}
						break;
						
					case ERROR:
						throw new ParseException( "Parse error!", offset);
					default:
						throw new ParseException( "Invalid state!", offset);
				}
				
				// end of file testing
				if( readInput == -1) {
					// finite state
					if(finiteStatesList.contains( (Integer) state)) {
						break;
					} else {
						throw new ParseException( "End of file in not-finite state!", offset);
					}
				}
				
				offset++;
				stateCharacter = readCharacter;
				state = nextState;
			}
			
			System.out.println( "parsing data complete.");
			
			//Close the input stream
			br.close();
		} catch (Exception e){//Catch exception if any
			System.err.println("Error: " + e.getMessage());
		}
		System.out.println( "Events found: " + events.size());
		
		return events;
	}
	
	@Override
	public byte[] encode(List<AlignEvent> events) {
		
		// kodovani eventu do cbr souboru
		StringBuffer sb = new StringBuffer( 200);
		for( Iterator<AlignEvent> i = events.iterator(); i.hasNext();) {
			sb.append(i.next());
		}
		return sb.toString().getBytes();
	}
	
	/**
	 * kontrola zda se jedna o valadni DNA symbol
	 * @param s
	 * @return
	 */
	private boolean isValidDNASymbol( char s) {
		
		try {
			st.parseToken( Character.toString(s));
			return true;
		} catch (IllegalSymbolException e) {
			return false; 
		}
	}
	
	/**
	 * test na konec automatu
	 * 
	 * @param c
	 * @return boolean
	 */
	private boolean isNextEvent( char c) {
		
		return ( Character.isDigit( c) || c == (char) -1);
	}
	
	public String toString() {
		return "String coder";
	}

}
